﻿describe('When creating a new Puzzle',
{
    before_each: function()
    {
        puzzle = new Puzzle();
    },
    
    'The Puzzle should have 4 Boxes': function()
    {
        value_of(puzzle.getAllBoxes().length).should_be(4);
    },

    'The Boxes should be in a 2x2 grid': function()
    {
        for (var columnIndex = 0; columnIndex < 2; columnIndex++)
        {
            for (var rowIndex = 0; rowIndex < 2; rowIndex++)
            {
                var box = puzzle.getBox(columnIndex, rowIndex);
                value_of(box.getColumnIndex()).should_be(columnIndex);
                value_of(box.getRowIndex()).should_be(rowIndex);
            }
        }
    },

    'No Boxes should be selected': function()
    {
        value_of(puzzle.getSelectedBox()).should_be_null();
    },
    
    'No Boxes should have a value': function()
    {
        var boxes = puzzle.getAllBoxes();
        for (var i = 0; i < boxes.length; i++)
            value_of(boxes[i].getValue()).should_be_null();
    }
});

describe('When a Box is clicked on',
{
    before_each: function()
    {
        puzzle = new Puzzle('unsel', 'sel');
        var boxes = puzzle.getAllBoxes();
        for (var i = 0; i < boxes.length; i++)
            boxes[i].setElement(function() { this.className = null; });
    },

    'The clicked Box should be the only selected Box in the Puzzle': function()
    {
        value_of(puzzle.getBox(0, 0).getIsSelected()).should_be_false();
        puzzle.getBox(0, 0).toggleSelection();
        value_of(puzzle.getBox(0, 0).getIsSelected()).should_be_true();
    },

    'The clicked Box should have the selected CSS class': function()
    {
        puzzle.getBox(0, 0).toggleSelection();
        value_of(puzzle.getBox(0, 0).getElement().className).should_be('sel');
    },
    
    'The unselected Boxes should have the unselected CSS class': function()
    {
        var box = puzzle.getBox(0, 0);
        box.toggleSelection();
        
        var boxes = puzzle.getAllBoxes();
        for (var i = 0; i < boxes.length; i++)
        {
            if (boxes[i] != box)
                value_of(boxes[i].getElement().className).should_be('unsel');
        }
    },
    
    'The Box that was previously selected should now have the unselected CSS class': function()
    {
        puzzle.getBox(0, 0).toggleSelection();
        value_of(puzzle.getBox(0, 0).getElement().className).should_be('sel');
        puzzle.getBox(0, 1).toggleSelection();
        value_of(puzzle.getBox(0, 0).getElement().className).should_be('unsel');
        value_of(puzzle.getBox(0, 1).getElement().className).should_be('sel');
    }
});

describe('When the user presses a key',
{
    before_each: function()
    {
        puzzle = new Puzzle();
        var boxes = puzzle.getAllBoxes();
        for (var i = 0; i < boxes.length; i++)
            boxes[i].setElement(function() { this.className = null; });
    },

    'The value should be displayed in the Box if the value is valid': function()
    {
        puzzle.getBox(0, 0).toggleSelection();
        puzzle.onKeyDown(49); // '1'
        value_of(puzzle.getSelectedBox().getValue()).should_be(1);
        puzzle.onKeyDown(50); // '2'
        value_of(puzzle.getSelectedBox().getValue()).should_be(2);
        puzzle.onKeyDown(51); // '3'
        value_of(puzzle.getSelectedBox().getValue()).should_be(3);
        puzzle.onKeyDown(52); // '4'
        value_of(puzzle.getSelectedBox().getValue()).should_be(4);
    },
    
    'The value should be ignored if no Box is selected': function()
    {
        puzzle.onKeyDown(49); // '1'
        var boxes = puzzle.getAllBoxes();
        for (var i = 0; i < boxes.length; i++)
        {
            value_of(boxes[i].getValue()).should_be_null();
        }
    },
    
    'The value in a Box should be cleared if the user hits the Backspace key': function()
    {
        puzzle.getBox(0, 0).toggleSelection();
        puzzle.onKeyDown(49); // '1'
        value_of(puzzle.getSelectedBox().getValue()).should_be(1);
        puzzle.onKeyDown(8); // 8 = ASCII code for Backspace key
        value_of(puzzle.getSelectedBox().getValue()).should_be_null();
    },
    
    'The value in a Box should be cleared if the user hits the Delete key': function()
    {
        puzzle.getBox(0, 0).toggleSelection();
        puzzle.onKeyDown(49); // '1'
        value_of(puzzle.getSelectedBox().getValue()).should_be(1);
        puzzle.onKeyDown(46); // Delete key
        value_of(puzzle.getSelectedBox().getValue()).should_be_null();
    }
});

describe('When setting the value in a Box',
{
    before_each: function()
    {
        box = new Box(0, 0);
        var element = new Object();
        element.innerHTML = null;
        box.setElement(element);
    },

    'The value in the Box should be cleared if the value is null': function()
    {
        box.setValue(null);
        value_of(box.getValue()).should_be_null();
        value_of(box.getElement().innerHTML).should_be('');
    },
    
    'An exception should be thrown if the value is not valid': function()
    {
        try
        {
            box.setValue('a');
            value_of(this).should_fail("Expected exception was not thrown");
        }
        catch (e)
        {
            value_of(e).should_not_be_null();
        }
    },
    
    'The value should be set if the value is valid': function()
    {
        for (var i = 1; i <= 4; i++)
        {
            box.setValue(i);
            value_of(box.getValue()).should_be(i);        
            value_of(box.getElement().innerHTML).should_be(i);
        }
    }    
});

describe('When assigning an element to a Box',
{
    before_each: function()
    {
        box = new Box(0, 0);
    },
    
    'The element should have the Box assigned to the box property': function()
    {
        var element = function() { };
        box.setElement(element);
        value_of(element.box).should_be(box);
    }
});

describe('When toggling the selection of a box',
{
    before_each: function()
    {
        box = new Box(0, 0);
        box.setElement(function() { this.className = ''; });
    },
    
    'The value should change from selected to not selected and vice versa': function()
    {
        var isSelected = box.getIsSelected();
        box.toggleSelection();
        value_of(box.getIsSelected()).should_be(!isSelected);
        box.toggleSelection();
        value_of(box.getIsSelected()).should_be(isSelected);
    },
    
    'Listeners should be notified': function()
    {
        var testValue = null;
        box.onIsSelectedChanged(function(sender) { testValue = 10; });
        box.toggleSelection();
        value_of(testValue).should_be(10);
    }
});

describe('When validating a puzzle',
{
    before_each: function()
    {
        puzzle = new Puzzle();
        var boxes = puzzle.getAllBoxes();
        for (var i = 0; i < boxes.length; i++)
            boxes[i].setElement(function() { this.className = null; });
    },

    'The Puzzle is invalid if any Box does not have a value': function()
    {
        puzzle.setAllValuesToOneExcept = function(box)
        {
            var boxes = puzzle.getAllBoxes();
            for (var i = 0; i < boxes.length; i++)
            {
                if (boxes[i] == box)
                    boxes[i].setValue(null);
                else
                    boxes[i].setValue(i + 1);
            }
        };

        for (var column = 0; column < 2; column++)
        {
            for (var row = 0; row < 2; row++)
            {
                puzzle.setAllValuesToOneExcept(puzzle.getBox(column, row));
                value_of(puzzle.isPuzzleValid()).should_be_false();
            }
        }
    },

    'The Puzzle is invalid if any Box has the same value as another Box': function()
    {
        puzzle.setDuplicateValue = function(box)
        {
            var boxes = puzzle.getAllBoxes();
            for (var i = 0; i < boxes.length; i++)
            {
                if (boxes[i] == box)
                    boxes[i].setValue(i == 3 ? 1 : i + 2);
                else
                    boxes[i].setValue(i + 1);
            }
        };

        for (var column = 0; column < 2; column++)
        {
            for (var row = 0; row < 2; row++)
            {
                puzzle.setDuplicateValue(puzzle.getBox(0, 0));
                value_of(puzzle.isPuzzleValid()).should_be_false();
            }
        }
    },

    'The Puzzle is valid if all Boxes have unique values': function()
    {
        var boxes = puzzle.getAllBoxes();
        for (var i = 0; i < boxes.length; i++)
        {
            boxes[i].setValue(i + 1);
        }
        value_of(puzzle.isPuzzleValid()).should_be_true();
    },

    'Should show an alert box with the result': function()
    {
        var alertMessage;
        puzzle.showAlert = function(message) { alertMessage = message; };
        puzzle.validate();
        value_of(alertMessage).should_be('The puzzle is invalid.');
    }
});

describe('When retrieving a new Puzzle',
{
    before_each: function()
    {
        puzzle = new Puzzle();
        puzzle.getNewPuzzleDataSynchronously = true;
        var boxes = puzzle.getAllBoxes();
        for (var i = 0; i < boxes.length; i++)
            boxes[i].setElement(function() { this.className = null; });
    },

    'Should load the information for a new Puzzle': function()
    {
        var boxHasData = false;
        puzzle.getNewPuzzleData();
        var allBoxes = puzzle.getAllBoxes();
        for (var i = 0; i < allBoxes.length; i++)
        {
            if (allBoxes[i].getValue() >= 1 && allBoxes[i].getValue() <= 4)
                boxHasData = true;
        }

        value_of(boxHasData).should_be_true();
    },

    'All Boxes that are not assigned a value in the new Puzzle should have no value': function()
    {
        for (var columnIndex = 0; columnIndex < 2; columnIndex++)
        {
            for (var rowIndex = 0; rowIndex < 2; rowIndex++)
            {
                puzzle.getBox(columnIndex, rowIndex).setValue(4);
            }
        }

        puzzle.getNewPuzzleData = function()
        {
            var result = new Array();
            result.push(new Array(0, 0, 1));
            result.push(new Array(1, 0, 3));
            puzzle.loadNewPuzzleData(result);
        };

        puzzle.loadNewPuzzle();
        value_of(puzzle.getBox(0, 0).getValue()).should_be(1);
        value_of(puzzle.getBox(0, 1).getValue()).should_be_null();
        value_of(puzzle.getBox(1, 0).getValue()).should_be(3);
        value_of(puzzle.getBox(1, 1).getValue()).should_be_null();
    },

    'Should load the new puzzle information into the current Puzzle': function()
    {
        puzzle.getNewPuzzleData = function()
        {
            var result = new Array();
            result.push(new Array(0, 0, 1));
            result.push(new Array(0, 1, 2));
            result.push(new Array(1, 0, 3));
            result.push(new Array(1, 1, 4));
            puzzle.loadNewPuzzleData(result);
        };

        puzzle.loadNewPuzzle();
        value_of(puzzle.getBox(0, 0).getValue()).should_be(1);
        value_of(puzzle.getBox(0, 1).getValue()).should_be(2);
        value_of(puzzle.getBox(1, 0).getValue()).should_be(3);
        value_of(puzzle.getBox(1, 1).getValue()).should_be(4);
    }
});

